package sipphone.example;


import gov.nist.javax.sip.address.SipUri;

import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.*;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.*;



/**
 * Created by RXJ on 2018/1/29.
 */
public class SipHandler implements SipListener {

    SipStack sipStack;

    SipFactory sipFactory;

    SipProvider sipProvider;

    String localHost;
    int localPort;

    String registrarHost = "192.168.1.218";
    int registrarPort = 5060;

    String username = "816";
    String password = "123456";

    private Vector requestListeners, responseListeners, dtmfListeners;

    String callID;

    long sequenceNum = 1L;

    boolean registered;

    String registrarDomain;

    private static SipHandler instance;

    public SipHandler(String localhost, int port) {
        try {
            sipFactory = SipFactory.getInstance();
            sipFactory.setPathName("gov.nist");
            this.localHost = localhost;
            this.localPort = port;
            requestListeners = new Vector();
            responseListeners = new Vector();
            dtmfListeners = new Vector();

            Properties properties = new Properties();
            properties.setProperty("javax.sip.STACK_NAME", "SipHandler");
            properties.setProperty("javax.sip.IP_ADDRESS", localhost);
            properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
            properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "SipHandler.txt");
            properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "SipHandlerDebug.log");

            sipStack = sipFactory.createSipStack(properties);

            ListeningPoint tcp = sipStack
                    .createListeningPoint(port, "tcp");
            ListeningPoint udp = sipStack
                    .createListeningPoint(port, "udp");

            sipProvider = sipStack.createSipProvider(tcp);
            sipProvider.addSipListener(this);
            sipProvider = sipStack.createSipProvider(udp);
            sipProvider.addSipListener(this);

            // Setup callID
            callID = System.currentTimeMillis() + "@" + localHost;

            if (getRegistrarHost() != null) {
                System.out.println("进行注册！");
                register();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 注册方法
     *
     * @throws PeerUnavailableException
     * @throws ParseException
     */
    public void register() throws PeerUnavailableException, ParseException {
        // Register with Registrar server
        if (registrarHost != null) {
            System.out.println("start Register");
            register(Request.REGISTER,
                    "sip:" + registrarHost,
                    "sip:" + username + "@" + registrarHost,
                    null, null,
                    sipFactory.createHeaderFactory().createCallIdHeader(callID));
        }
    }

    /**
     * 注册请求方法
     *
     * @param type
     * @param registrarURI
     * @param to
     * @param authnHeader
     * @param callID
     */
    public void register(String type, String registrarURI, String to,
                         AuthorizationHeader authnHeader, ViaHeader viaHeader, CallIdHeader callID) {
        try {
            SipURI requestURI = sipFactory.createAddressFactory().createSipURI(username, registrarHost);

            //创建 本地请求头 from
            Address fromNameAddress = sipFactory.createAddressFactory().createAddress(requestURI);
            fromNameAddress.setDisplayName(username);
            FromHeader fromHeader = sipFactory.createHeaderFactory().createFromHeader(
                    fromNameAddress, null);

            //创建 本地请求头 to
            Address toNameAddress = sipFactory.createAddressFactory().createAddress(requestURI);
            toNameAddress.setDisplayName(username);
            //to 请求头
            ToHeader toHeader = sipFactory.createHeaderFactory().createToHeader(
                    toNameAddress, null);
            //创建 cSeq  Request 为调用方法
            CSeqHeader cSeqHeader =
                    sipFactory.createHeaderFactory().createCSeqHeader(sequenceNum++, type);
            //创建  Max_forwards 默认：70
            MaxForwardsHeader maxForwards =
                    sipFactory.createHeaderFactory().createMaxForwardsHeader(70);

            Request registerRequest = sipFactory.createMessageFactory().createRequest(
                    requestURI,
                    type,
                    callID,
                    cSeqHeader,
                    fromHeader,
                    toHeader,
                    new ArrayList(),
                    maxForwards);

            ContactHeader contactHeader = sipFactory.createHeaderFactory()
                    .createContactHeader(
                            sipFactory.createAddressFactory().createAddress(
                                    "sip:" + getUsername() + "@"
                                            + localHost
                                            + ":"
                                            + localPort));
            registerRequest.addHeader(contactHeader);

            if (authnHeader != null) {
                registerRequest.addHeader(authnHeader);
                registerRequest = setAllowHeader_INVITE(registerRequest);
            } else {
                //创建 Allow对象
                registerRequest = setAllowHeader_REGISTER(registerRequest);
            }
            if (viaHeader == null) {
                viaHeader = sipFactory.createHeaderFactory().createViaHeader(
                        localHost, localPort, "udp", null);
                registerRequest.addHeader(viaHeader);
            }

            //创建 userAgent
            List<String> userAgentList = new ArrayList<>();
            userAgentList.add("eyeBeam ");
            userAgentList.add("release ");
            userAgentList.add("1011d ");
            userAgentList.add("stamp ");
            userAgentList.add("40820 ");
            UserAgentHeader userAgentHeader = getSipFactory().createHeaderFactory()
                    .createUserAgentHeader(userAgentList);
            registerRequest.addHeader(userAgentHeader);

            //创建 Expires
            ExpiresHeader expiresHeader = getSipFactory().createHeaderFactory()
                    .createExpiresHeader(3600);
            registerRequest.addHeader(expiresHeader);
            System.out.println("进行注册请求:");
            System.out.println(registerRequest);

            getSipProvider().sendRequest(registerRequest);

        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void addResponseListener(ResponseListener listener) {
        responseListeners.addElement(listener);
    }

    public void hangup(String recipient) {
        try {
            sendRequest(recipient, Request.BYE, null, null);
        } catch (PeerUnavailableException e) {
            System.out.println(e);
        } catch (TransactionUnavailableException e) {
            System.out.println(e);
        } catch (ParseException e) {
            System.out.println(e);
        } catch (InvalidArgumentException e) {
            System.out.println(e);
        } catch (SipException e) {
            System.out.println(e);
        }
    }

    @Override
    public void processRequest(RequestEvent requestEvent) {
        Request request = requestEvent.getRequest();

        if (null == request) {
            System.out.println("processRequest request is null.");
            return;
        }
        if (Request.BYE.equalsIgnoreCase(request.getMethod())) {
            System.out.println("Request Bye :\n" + request);
        } else {
            System.out.println(requestEvent);
        }
    }

    @Override
    public void processResponse(ResponseEvent responseEvent) {
        Response response = responseEvent.getResponse();
        System.out.println("Get a response");
        System.out.println(response);

        String method = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)).getMethod();
        if (response.getStatusCode() == Response.OK) {
            if (method.equals(Request.REGISTER)) {
                setRegistered(true);
                System.out.println("注册成功");
            }
            if(method.equals(Request.INVITE)){
                if(responseListeners.size()>0){
                    for(int i =0;i<responseListeners.size();i++){
                        ResponseListener listener = (ResponseListener) responseListeners.get(i);
                        if(listener != null){
                            System.out.println("进行INVITE 处理");
                            listener.handleResponse(responseEvent);
                        }
                    }

                }

            }
        }
        if (response.getStatusCode() == 401) {
            try {
                handleAuthorization(responseEvent, response, method);
            } catch (PeerUnavailableException e) {
                e.printStackTrace();
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }else if (response.getStatusCode() == 407) {
            handleProxyAuthorization(responseEvent, response, method);
        }
    }

    @Override
    public void processTimeout(TimeoutEvent timeoutEvent) {
        System.out.println("processTimeout:\n" + timeoutEvent);
    }

    @Override
    public void processIOException(IOExceptionEvent ioExceptionEvent) {
        System.out.println("processIOException:\n" + ioExceptionEvent);
    }

    @Override
    public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
        System.out.println("processTransactionTerminated:\n");
        ClientTransaction ct = transactionTerminatedEvent.getClientTransaction();
        ServerTransaction st = transactionTerminatedEvent.getServerTransaction();
        System.out.println(ct.getRequest());
        System.out.println(st.getRequest());
    }

    @Override
    public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
        System.out.println("processDialogTerminated:\n" + dialogTerminatedEvent);
    }

    /**
     * 注册401 处理 密码验证方法
     *
     * @param responseEvent
     * @param response
     * @param method
     */
    public void handleAuthorization(ResponseEvent responseEvent,
                                    Response response, String method) throws PeerUnavailableException, ParseException {

        //创建 CALL_ID 根据 response 中获取
        CallIdHeader callIdHeader = (CallIdHeader) response.getHeader("Call-ID");

        // 从WWW-Authenticate header 获取信息
        WWWAuthenticateHeader wwwAuthenticateHeader = (WWWAuthenticateHeader)
                response.getHeader("WWW-Authenticate");
        String realm = wwwAuthenticateHeader.getRealm();
        String nonce = wwwAuthenticateHeader.getNonce();

        //从获取到的加密信息中保存到 验证信息中
        AuthorizationHeader authorizationHeader =
                getSipFactory().createHeaderFactory()
                        .createAuthorizationHeader("sip:" + registrarHost);
        authorizationHeader.setScheme(wwwAuthenticateHeader.getScheme());
        authorizationHeader.setUsername(username);
        authorizationHeader.setRealm(realm);
        authorizationHeader.setNonce(nonce);
        authorizationHeader.setAlgorithm(wwwAuthenticateHeader.getAlgorithm());

        SipUri sipUri = (SipUri) sipFactory.createAddressFactory()
                .createURI("sip:" + registrarHost);
        authorizationHeader.setURI(sipUri);
        //计算 本地密码 获取 response
        authorizationHeader.setResponse(SipHandler.getResponse(
                username, realm, password, method, "sip:" + registrarHost, nonce));

        System.out.println("Register 401 处理后请求 Register");
        register(Request.REGISTER,
                "sip:" + username + "@" + registrarHost,
                "sip:" + getUsername() + "@" + localHost + ":" + localPort,
                authorizationHeader,
                null,
                callIdHeader);
    }


    /**
     * 407 验证处理
     * @param responseEvent
     * @param response
     * @param method
     */
    private void handleProxyAuthorization(ResponseEvent responseEvent, Response response, String method) {
        // Handle Unauthorized
        Header authenticateHeader = response.getHeader("Proxy-Authenticate");
        if (authenticateHeader instanceof ProxyAuthenticateHeader) {
            try {
                //创建 CALL_ID 根据 response 中获取
                CallIdHeader callIdHeader = (CallIdHeader) response.getHeader("Call-ID");
                ProxyAuthenticateHeader wwwAuthnHeader = (ProxyAuthenticateHeader)authenticateHeader;

                SipUri sipUri = (SipUri) sipFactory.createAddressFactory()
                        .createURI("sip:" + registrarHost);
                AuthorizationHeader authorization = sipFactory.createHeaderFactory().createProxyAuthorizationHeader(wwwAuthnHeader.getScheme());

                authorization.setUsername(getUsername());
                authorization.setRealm(wwwAuthnHeader.getRealm());
                authorization.setNonce(wwwAuthnHeader.getNonce());
                authorization.setParameter("uri",sipUri.toString());
                String digest=SipHandler.getResponse(
                        getUsername(), wwwAuthnHeader.getRealm(), password,
                        method, sipUri.toString(), wwwAuthnHeader.getNonce());
                authorization.setResponse(digest);
                if(wwwAuthnHeader.getAlgorithm() != null){
                    authorization.setAlgorithm(wwwAuthnHeader.getAlgorithm());
                }
                if(wwwAuthnHeader.getOpaque() != null){
                    authorization.setOpaque(wwwAuthnHeader.getOpaque());
                }


                register(Request.REGISTER,
                        "sip:" + username + "@" + registrarHost,
                        "sip:" + getUsername() + "@" + localHost + ":" + localPort,
                        authorization,
                        null,
                        callIdHeader);
            } catch (PeerUnavailableException e) {
                System.out.println(e);
            } catch (ParseException e) {
                System.out.println(e);
            }
        }
    }
    /**
     * 设置 allowHeader 授权 register方法
     *
     * @param request
     * @return
     * @throws ParseException
     */
    public Request setAllowHeader_REGISTER(Request request) throws ParseException, PeerUnavailableException {
        String[] allowHeaderList = {
                Request.REGISTER, Request.ACK, Request.CANCEL, Request.OPTIONS,
                Request.BYE, Request.REFER, Request.NOTIFY, Request.MESSAGE, Request.SUBSCRIBE,
                Request.INFO};
        for (int i = 0; i < allowHeaderList.length; i++) {
            AllowHeader allowHeader = getSipFactory().createHeaderFactory()
                    .createAllowHeader(allowHeaderList[i]);
            request.addHeader(allowHeader);
        }
        return request;
    }

    /**
     * 发送请求公共方法
     * @param recipient
     * @param type
     * @param content
     * @param contentType
     * @throws SipException
     * @throws ParseException
     * @throws InvalidArgumentException
     */
    public void sendRequest (String recipient, String type, String content, ContentTypeHeader contentType) throws SipException, ParseException , InvalidArgumentException {
        URI uri = sipFactory.createAddressFactory().createURI(recipient);
        Request request = sipFactory.createMessageFactory().createRequest(
                uri,
                type,
                sipFactory.createHeaderFactory().createCallIdHeader(getCallID()),
                sipFactory.createHeaderFactory().createCSeqHeader(sequenceNum++, type),
                sipFactory.createHeaderFactory().createFromHeader(
                sipFactory.createAddressFactory().createAddress(
                                "sip:"+username+"@"
                                        + SipHandler.getInstance()
                                        .getLocalHost()
                                        + ":"
                                        + SipHandler.getInstance()
                                        .getLocalPort()),
                        Integer.toString(hashCode())),
                sipFactory.createHeaderFactory().createToHeader(
                        sipFactory.createAddressFactory().createAddress(
                                recipient), null),
                new ArrayList(),
                sipFactory.createHeaderFactory().createMaxForwardsHeader(10));

        ContactHeader contactHeader = sipFactory.createHeaderFactory()
                .createContactHeader(
                        sipFactory.createAddressFactory().createAddress(
                                "sip:"+username+"@"
                                        + SipHandler.getInstance()
                                        .getLocalHost()
                                        + ":"
                                        + SipHandler.getInstance()
                                        .getLocalPort()));
        request.addHeader(contactHeader);

        if (content != null) {
            request.setContent(content, contentType);
        }

        ViaHeader   viaHeader = sipFactory.createHeaderFactory().createViaHeader(
                    localHost, localPort, "udp", null);
        request.addHeader(viaHeader);
       getSipProvider().sendRequest(request);

    }

    public void sendRequest (Request request) throws SipException, ParseException , InvalidArgumentException {
        getSipProvider().sendRequest(request);
    }
    /**
     * 设置 allowHeader 授权 register 401方法
     *
     * @param request
     * @return
     * @throws ParseException
     */
    public Request setAllowHeader_INVITE(Request request) throws ParseException, PeerUnavailableException {
        String[] allowHeaderList = {
                Request.INVITE, Request.ACK, Request.CANCEL, Request.OPTIONS,
                Request.BYE, Request.REFER, Request.NOTIFY, Request.MESSAGE, Request.SUBSCRIBE,
                Request.INFO};
        for (int i = 0; i < allowHeaderList.length; i++) {
            AllowHeader allowHeader = getSipFactory().createHeaderFactory()
                    .createAllowHeader(allowHeaderList[i]);
            request.addHeader(allowHeader);
        }
        return request;
    }



    public String getRegistrarHost() {
        return registrarHost;
    }

    public String getUsername() {
        return username;
    }

    public SipProvider getSipProvider() {
        return sipProvider;
    }

    public SipFactory getSipFactory() {
        return sipFactory;
    }

    /**
     * 第二步注册时 根据 第一次返回的验证信息计算 验证信息中的 response值
     *
     * @param username 用户名
     * @param realm    realm
     * @param password 密码
     * @param method   方法:"REGISTER"
     * @param uri      请求地址 ： sip:192.168.1.218
     * @param nonce    请求认证码
     * @return response 计算后的值
     */
    public static String getResponse(
            String username, String realm, String password, String method,
            String uri, String nonce) {
        String hex1 = MD5Util.EncoderByM5(username + ":" + realm + ":" + password);
        String hex2 = MD5Util.EncoderByM5(method + ":" + uri);
        String result = MD5Util.EncoderByM5(hex1 + ":" + nonce + ":" + hex2);
        return result;
    }

    public void setRegistered(boolean registered) {
        this.registered = registered;
    }

    public static SipHandler getInstance() {
        return instance;
    }

    public static SipHandler getInstance(String remoteHost, int port) {
        if (instance == null) {
            instance = new SipHandler(remoteHost, port);
        }

        return instance;
    }

    public String getCallID() {
        return callID;
    }

    public void setCallID(String callID) {
        this.callID = callID;
    }

    public String getLocalHost() {
        return localHost;
    }

    public void setLocalHost(String localHost) {
        this.localHost = localHost;
    }

    public int getLocalPort() {
        return localPort;
    }

    public void setLocalPort(int localPort) {
        this.localPort = localPort;
    }

    public void setRegistrarHost(String registrarHost) {
        this.registrarHost = registrarHost;
    }

    public SipStack getSipStack() {
        return sipStack;
    }

    public void setSipStack(SipStack sipStack) {
        this.sipStack = sipStack;
    }

    public void setSipFactory(SipFactory sipFactory) {
        this.sipFactory = sipFactory;
    }

    public void setSipProvider(SipProvider sipProvider) {
        this.sipProvider = sipProvider;
    }

    public int getRegistrarPort() {
        return registrarPort;
    }

    public void setRegistrarPort(int registrarPort) {
        this.registrarPort = registrarPort;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public long getSequenceNum() {
        return sequenceNum;
    }

    public void setSequenceNum(long sequenceNum) {
        this.sequenceNum = sequenceNum;
    }

    public boolean isRegistered() {
        return registered;
    }

    public String getRegistrarDomain() {
        return registrarDomain;
    }

    public void setRegistrarDomain(String registrarDomain) {
        this.registrarDomain = registrarDomain;
    }
    /*
    public static void main(String[] agrs) {
        int pid = 0;
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        String name = runtime.getName();
        System.out.println("当前进程的标识为：" + name);
        int index = name.indexOf("@");
        if (index != -1) {
            pid = Integer.parseInt(name.substring(0, index));
            System.out.println("当前进程的PID为：" + pid);
        }
        try {
            int port = pid;
            String ip = InetAddress.getLocalHost().getHostAddress();
            System.out.println("--port = " + port + "\n--ip = " + ip);
            SipHandler sipHandler = new SipHandler(ip, port);
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }*/
}
